1 module hip.audio.backend.openal.source;
2 version(OpenAL):
3 
4 import hip.audio.backend.openal.clip;
5 import hip.error.handler;
6 import hip.audio;
7 import hip.audio.audiosource;
8 import hip.util.memory;
9 import hip.audio.backend.openal.al_err;
10 import hip.audio.clip;
11 import bindbc.openal;
12 
13 
14 /**
15 * Constant used for making the panning distance offset from the listener
16 */
17 enum ALfloat PANNING_CONSTANT = 1000;
18 
19 public class HipOpenALAudioSource : HipAudioSource
20 {
21     import hip.console.log;
22     //id is created from OpenAL player
23     uint id;
24     bool isStreamed;
25 
26     this(bool isStreamed)
27     {
28         this.isStreamed=isStreamed;
29         alGenSources(1, &id);
30         alCheckError("Error creating OpenAL source");
31         alSourcef(id, AL_GAIN, 1);
32         alSourcef(id, AL_PITCH, 1);
33         alSource3f(id, AL_POSITION, 0f, 0f, 0f);
34         alSource3f(id, AL_VELOCITY, 0f, 0f, 0f);
35     	alSourcei(id, AL_LOOPING, AL_FALSE);
36         alCheckError("Error setting OpenAL source properties");
37     }
38 
39     alias pitch = AHipAudioSource.pitch;
40     alias panning = AHipAudioSource.panning;
41     alias volume = AHipAudioSource.volume;
42     alias pitch = AHipAudioSource.pitch;
43     alias clip = HipAudioSource.clip;
44     alias position = AHipAudioSource.position;
45     alias loop = AHipAudioSource.loop;
46 
47 
48     override float pitch(float value)
49     {
50         auto ret = super.pitch(value);
51         if(isDirty && isPlaying)
52         {
53             alSourcef(id, AL_PITCH, ret);
54             alCheckError("Error setting OpenAL source pitch");
55             isDirty = false;
56         }
57         return ret;
58     }
59 
60     override float panning(float value)
61     {
62         auto ret = super.panning(value);
63         if(isDirty && isPlaying)
64         {
65             isDirty = false;
66             alSource3f(id, AL_POSITION, position[0] + (ret*PANNING_CONSTANT), position[1], position[2]);
67             alCheckError("Error setting OpenAL source position/panning");
68         }
69         return ret;
70     }
71     override float volume(float value)
72     {
73         auto ret  = super.volume(value);
74         if(isDirty && isPlaying)
75         {
76             isDirty = false;
77             alSourcef(id, AL_GAIN, ret);
78             alCheckError("Error setting OpenAL source volume");
79         }
80         return ret;
81     }
82 
83     override float[3] position(float[3] value)
84     {
85         auto ret = super.position(value);
86         if(isDirty && isPlaying)
87         {
88             isDirty = false;
89             alSource3f(id, AL_POSITION, ret[0] + (panning*PANNING_CONSTANT), ret[1], ret[2]);
90             alCheckError("Error setting OpenAL source position/panning");
91         }
92         return ret;
93     }
94 
95     override bool loop(bool value)
96     {
97         bool ret = super.loop(value);
98         if(isDirty && isPlaying)
99         {
100     	    alSourcei(id, AL_LOOPING, ret ? AL_TRUE : AL_FALSE);
101             alCheckError("Error setting OpenAL loop");
102         }
103         return ret;
104     }
105 
106     public void setDistanceModel(DistanceModel model)
107     {
108         alDistanceModel(getALDistanceModel(model));
109         alCheckError("Error setting OpenAL source distance model");
110     }
111     /**
112     * After the max distance, the volume won't decrease anymore
113     */
114     public void setMaxDistance(float dist)
115     {
116         alSourcef(id, AL_MAX_DISTANCE, dist);
117         alCheckError("Error setting OpenAL source max distance");
118     }
119 
120     /**
121     * Sets the distance where the volume will be equal to 1
122     */
123     void setReferenceDistance(float dist)
124     {
125         alSourcef(id, AL_REFERENCE_DISTANCE, dist);
126         alCheckError("Error setting OpenAL source reference distance");
127     }
128     /**
129     * The factor which the sound volume decreases when the distance is greater
130     * than the reference
131     */
132     public void setRolloffFactor(float factor)
133     {
134         alSourcef(id, AL_ROLLOFF_FACTOR, factor);
135         alCheckError("Error setting OpenAL source rolloff factor");
136     }
137 
138     public void setVelocity(in float[3] vel)
139     {
140         alSource3f(id, AL_VELOCITY, vel[0], vel[1], vel[2]);
141         alCheckError("Error setting OpenAL source velocity");
142 
143     }
144     void setDoppler(in float[3] vel)
145     {
146         alSource3f(id, AL_DOPPLER_VELOCITY, vel[0], vel[1], vel[2]);
147         alCheckError("Error setting OpenAL source doppler factor");
148     }
149 
150     override bool play()
151     {
152         HipOpenALClip clp = clip.getAudioClipBackend!(HipOpenALClip);
153         if(clp.hasBuffer)
154         {
155             if(isDirty)
156             {
157                 isDirty = false;
158                 alSourcef(id, AL_PITCH, pitch);
159                 alCheckError("Error setting OpenAL source pitch");
160                 alSourcef(id, AL_GAIN, volume);
161                 alCheckError("Error setting OpenAL source volume");
162                 alSource3f(id, AL_POSITION, position[0] + (panning*PANNING_CONSTANT), position[1], position[2]);
163                 alCheckError("Error setting OpenAL source position/panning");
164                 alSourcei(id, AL_LOOPING, loop ? AL_TRUE : AL_FALSE);
165                 alCheckError("Error setting OpenAL loop");
166             }
167             alSourcePlay(id);
168             alCheckError("Error querying OpenAL play");
169             return true;
170         }
171         else
172             ErrorHandler.showErrorMessage("Tried to play without a buffer", "");
173         return false;
174     }
175 
176     public bool resume()
177     {
178         if(!isPlaying)
179         {
180             alSourcePlay(id);
181             alCheckError("Error querying OpenAL play");
182             return true;
183         }
184         return false;
185     }
186 
187 
188     override bool stop()
189     {
190         alSourceStop(id);
191         alCheckError("Error querying OpenAL stop");
192         return false;
193     }
194 
195     override bool pause()
196     {
197         alSourcePause(id);
198         alCheckError("Error querying OpenAL pause");
199         return false;
200     }
201     override bool play_streamed(){return play();}
202 
203 
204     override IHipAudioClip clip(IHipAudioClip newClip)
205     {
206         super.clip(newClip);
207         // if(!newClip.isStreamed)
208         // {
209         //     HipAudioBuffer buf = getBufferFromAPI(newClip);
210         //     alSourcei(id, AL_BUFFER, buf.al);
211         // }
212         // else
213         {
214             HipAudioBuffer buf = getBufferFromAPI(newClip); //use clip.chunkSize in future
215             alSourceQueueBuffers(id, 1, &buf.al);
216         }
217         logln(id);
218         return newClip;
219     }
220 
221 
222     override void pullStreamData()
223     {
224         ErrorHandler.assertExit(clip !is null, "Can't pull stream data without any buffer attached");
225         ErrorHandler.assertExit(id != 0, "Can't pull stream data without source id");
226 
227         HipAudioBuffer buffer;
228         int processed;
229         alGetSourcei(id, AL_BUFFERS_PROCESSED, &processed);//Gets the queueId
230         buffer.al = cast(uint)processed;
231         if(buffer.al != 0)
232         {
233             //Returns the bufferId to freeBuf
234             alSourceUnqueueBuffers(id, 1, &buffer.al);
235             (cast(HipAudioClip)this.clip).setBufferAvailable(buffer);
236         }
237         clip.updateStream();
238 
239         HipOpenALClip c = cast(HipOpenALClip)clip;
240         buffer = c.getBuffer(c.getClipData(), c.chunkSize);
241         alSourceQueueBuffers(id, 1, &buffer.al);
242 
243     }
244 
245 
246     uint getALFreeBuffer()
247     {
248         int b;
249         alGetSourcei(id, AL_BUFFERS_PROCESSED, &b);
250         return cast(uint)b;
251     }
252 
253     override HipAudioBufferWrapper* getFreeBuffer()
254     {
255         HipAudioBuffer buffer;
256         int b;
257         alGetSourcei(id, AL_BUFFERS_PROCESSED, &b);
258         buffer.al = cast(uint)b;
259         if(b == 0)
260             return null;
261         return (cast(HipAudioClip)clip).findBuffer(buffer);
262     }
263 
264 
265 
266     ~this()
267     {
268         logln("HipAudioSource Killed!");
269         alDeleteSources(1, &id);
270         id = 0;
271     }
272 }
273 
274 ALenum getALDistanceModel(DistanceModel model)
275 {
276     final switch(model) with(DistanceModel)
277     {
278         case DISTANCE_MODEL: return AL_DISTANCE_MODEL;
279         case INVERSE: return AL_INVERSE_DISTANCE;
280         case INVERSE_CLAMPED: return AL_INVERSE_DISTANCE_CLAMPED;
281         case LINEAR: return AL_LINEAR_DISTANCE;
282         case LINEAR_CLAMPED: return AL_LINEAR_DISTANCE_CLAMPED;
283         case EXPONENT: return AL_EXPONENT_DISTANCE;
284         case EXPONENT_CLAMPED: return AL_EXPONENT_DISTANCE_CLAMPED;
285     }
286 }